import Map, {Entry} from "./Map";
import Set from "../set/Set";
import Collection from "../core/Collection";
import HashSet from "../set/HashSet";
import ArrayList from "../list/ArrayList";
import Obj, {equalsTo} from "../core/Obj";
import FullCapacityException from "../exceptions/FullCapacityException";
import IllegalArgumentException from "../exceptions/IllegalArgumentException";
import UnmodifiableSet from "../set/UnmodifiableSet";
import UnmodifiableList from "../list/UnmodifiableList";
import Arrays from "../utils/Arrays";
import {hash, hashArray} from "../core/Hash";

const className = "hashMap";

/**
 * {@link Map}接口的基于{@link hash}的实现。此类不保证映射的顺序。
 *
 * 此实现为基本操作（获取和放置）提供恒定时间性能。每个传入的键都会被hash换算成hashCode，并以此为比较手段，进行键之间进行区分。
 *
 * 两个对象的HashCode相同，并不一定表示两个对象就相同，即{@link Obj#equalsTo}不一定为true。具体内容请参阅{@link hash}。
 *
 * @param K 存入地图的键类型
 * @param V 存入地图的值类型
 * @see Map
 * @see hash
 * @version 1.0
 */
export default class HashMap<K, V> extends Obj implements Map<K, V> {

  /**
   * 用于存数组的容器，用于存储键值对.
   *
   * @private
   * @version 1.0
   */
  private readonly _table: HashMapNode<K, V>[];

  /**
   * 此映射可以存储的元素数量。
   *
   * @private
   * @version 1.0
   */
  private _capacity: number;

  /**
   * 构造一个具有默认元素和容量的 HashMap。
   *
   * 如果您不提供默认元素，它将不包含任何内容。
   * 如果您不提供默认容量，它会将容量设置为无限。
   *
   * @param entries 需要默认置入此映射的元素
   * @param capacity 此映射的容量
   * @throws {FullCapacityException} 这个映射无法容纳这些元素
   * @throws {IllegalArgumentException} 这个容量数字不正确
   * @version 1.0
   */
  public constructor(entries: Entry<K, V>[] = [], capacity: number = -1) {
    super();

    if (capacity !== -1 && entries.length > capacity) {
      throw new FullCapacityException(className, capacity);
    }

    if (capacity < -1) {
      throw new IllegalArgumentException("capacity should be bigger than -1");
    }

    this._table = [];
    this._capacity = capacity;

    for (let entry of entries) {
      this.put(entry.getKey(), entry.getValue() as V);
    }
  }

  [Symbol.iterator](): Iterator<HashMapNode<K, V>> {
    return this._table[Symbol.iterator]();
  }

  /**
   * 返回此映射的容量。
   *
   * @return {number} 返回此映射的容量
   * @version 1.0
   */
  capacity(): number {
    return this._capacity;
  }

  /**
   * 清空这个映射。
   *
   * @version 1.0
   */
  public clear(): void {
    this._table.splice(0);
  }

  /**
   * 如果此映射包含指定键的映射，则返回 true。（最多可以有一个这样的映射。）
   *
   * @param key 要测试的键
   * @return {boolean} 如果存在相同的键，则返回 true
   * @version 1.0
   */
  public containsKey(key: K): boolean {
    return this.getNode(key) !== undefined;
  }

  /**
   * 如果此映射包含指定值的映射，则返回 true。
   *
   * @param value 要测试的值
   * @return {boolean} 如果存在相同的值，则返回 true
   * @version 1.0
   */
  public containsValue(value: V): boolean {
    return this._table.findIndex(hashMapNode => equalsTo(value, hashMapNode.getValue())) !== -1;
  }

  /**
   * 返回此映射中包含的完整键值对的{@link Set}视图。
   *
   * 该集合由映射支持，因此对映射的更改会反映在该集合中，反之亦然。
   *
   * 如果在对集合进行迭代的同时修改了映射，则迭代的结果将随之发生变化的。
   *
   * @return {Set<K>} 一个包含了映射中所有值的集合
   * @see Set
   * @version 1.0
   */
  public entrySet(): Set<Entry<K, V>> {
    return new UnmodifiableSet(new HashSet(this._table));
  }

  /**
   * 对此映射中的每一个键值对执行给定的操作，直到处理完所有条目或该操作引发异常。
   *
   * @param forEachFunc 对每一个键值对执行的操作
   * @version 1.0
   */
  public forEach(forEachFunc: (key: K, value: V) => void) {
    for (let tableElement of this._table) {
      forEachFunc(tableElement.getKey(), tableElement.getValue());
    }
  }

  public equals(b: Obj): boolean {
    if (b instanceof HashMap) {
      return Arrays.equals(this._table, b._table);
    }
    return false;
  }

  /**
   * 返回指定键映射到的值，如果此映射不包含键的映射，则返回 undefined。（最多可以有一个这样的映射。）
   *
   * @param key 要查找的键
   * @return {V | undefined} 查找到的对应的值。如果没有对应的映射，则返回undefined
   * @version 1.0
   */
  public get(key: K): V | undefined {
    const e = this.getNode(key);
    return e === undefined ? undefined : e.getValue();
  }

  /**
   * 返回指定键映射到的值，如果此映射不包含键的映射，则返回defaultValue。
   *
   * @param key 要查找的键
   * @param defaultValue 当查找不到时返回的默认值
   * @return {V} 返回指定键映射到的值，如果此映射不包含键的映射，则返回defaultValue
   * @version 1.0
   */
  public getOrDefault(key: K, defaultValue: V): V {
    const e = this.getNode(key);
    return e === undefined ? defaultValue : e.getValue();
  }

  /**
   * 增加此映射的容量使其可以容纳更多的元素。
   *
   * 给出的容量必须比现有的容量更大，否则将会抛出{@link IllegalArgumentException}。当容量被设定为-1时，表示此映射容量无穷大，之后将不能再增
   * 加容量，也将不再判断容量是否充满。
   *
   * @param capacity 新的容量
   * @throws {IllegalArgumentException} 新的容量比当前容量小或容量已无法增长
   * @version 1.0
   */
  public grow(capacity: number) {
    if (capacity < this._capacity && capacity !== -1) {
      throw new IllegalArgumentException(
        "capacity " + capacity + " is smaller than current capacity " + this._capacity + " and is not -1");
    } else {
      this._capacity = capacity;
      return true;
    }
  }

  public hashCode(): number {
    return hashArray(this._table);
  }

  /**
   * 返回此映射是否为空。
   *
   * @return {boolean} 如果此映射不包含键值映射，则返回 true
   * @version 1.0
   */
  public isEmpty(): boolean {
    return this._table.length === 0;
  }

  /**
   * 返回此映射是否已满。
   *
   * @return {boolean} 如果此映射已满，则返回 true
   * @version 1.0
   */
  public isFull(): boolean {
    return this._table.length === this._capacity;
  }

  /**
   * 用这个映射中的键值对的键填充一个数组并返回它。
   *
   * @return {K[]}一个包含全部键值对中键的数组
   * @version 1.0
   */
  public keyArray(): K[] {
    return this._table.map(hashMapNode => hashMapNode.getKey());
  }

  /**
   * 返回此映射中包含的键的{@link Set}视图。该视图中不存在重复元素。
   *
   * 该集合由映射支持，因此对映射的更改会反映在该集合中，反之亦然。
   *
   * 如果在对集合进行迭代的同时修改了映射，则迭代的结果将随之发生变化的。
   *
   * @return {Set<K>} 一个包含了映射中所有键的集合
   * @see Set
   * @version 1.0
   */
  public keySet(): Set<K> {
    return new HashSet(this.keyArray());
  }

  /**
   * 将指定值与此映射中的指定键相关联。
   *
   * 如果映射先前包含键的映射，则旧值将替换为指定值。
   *
   * @param key 需要被管理的键
   * @param value 需要被管理那的值
   * @return {V | undefined} 如果已经存在该键，则返回旧值。如果不存在，则返回undefined
   * @throws {FullCapacityException} 这个映射满了
   * @version 1.0
   */
  public put(key: K, value: V): V | undefined {
    const index = this._table.findIndex(hashMapNode => equalsTo(key, hashMapNode.getKey()));

    if (index === -1) {
      if (!this.isFull()) {
        this._table.push(HashMapNode.of(key, value));
        return undefined;
      } else {
        throw new FullCapacityException(className, this._capacity);
      }
    } else {
      const oldValue = this._table[index].getValue();
      this._table[index].setValue(value);
      return oldValue;
    }
  }

  /**
   * 如果指定的键尚未与值关联，则将其与给定值关联，否则放弃。
   *
   * @param key 需要关联的键
   * @param value 需要关联的值
   * @throws {FullCapacityException} 映射已经满了
   * @version 1.0
   */
  public putIfAbsent(key: K, value: V): void {
    const index = this._table.findIndex(hashMapNode => equalsTo(key, hashMapNode.getKey()));

    if (index === -1) {
      if (!this.isFull()) {
        this._table.push(HashMapNode.of(key, value));
        return undefined;
      } else {
        throw new FullCapacityException(className, this._capacity);
      }
    }
  }

  /**
   * 从此映射中删除指定键的映射（如果存在）。（地图最多可以包含一个这样的映射。）
   *
   * 返回此映射先前与键关联的值，如果映射不包含键的映射，则返回 undefined。
   *
   * @param key 需要被删除的键
   * @return {V | undefined} 如果指定的键存在，则返回被删除的映射的值。如果不存在，则返回undefined
   * @version 1.0
   */
  public remove(key: K): V | undefined {
    const index = this._table.findIndex(hashMapNode => equalsTo(key, hashMapNode.getKey()));
    if (index === -1) {
      return undefined;
    } else {
      return this._table.splice(index, 1)[0].getValue();
    }
  }

  /**
   * 返回此映射中的键值映射数。
   *
   * @return {number} 返回此映射中的键值映射数
   * @version 1.0
   */
  public size(): number {
    return this._table.length;
  }

  public toString(): string {
    return this._table.toString();
  }

  /**
   * 用这个映射中的键值对的值填充一个数组并返回它。
   *
   * @return {K[]}一个包含全部键值对中值的数组
   * @version 1.0
   */
  public valueArray(): V[] {
    return this._table.map(hashMapNode => hashMapNode.getValue());
  }

  /**
   * 返回此映射中包含的值的{@link Collection}视图。
   *
   * 该集合由映射支持，因此对映射的更改会反映在该集合中，反之亦然。
   *
   * 如果在对集合进行迭代的同时修改了映射，则迭代的结果将随之发生变化的。
   *
   * @return {Collection<K>} 一个包含了映射中所有值的集合
   * @see Collection
   * @version 1.0
   */
  public values(): Collection<V> {
    return new UnmodifiableList(new ArrayList(this.valueArray()));
  }

  private getNode(key: K): HashMapNode<K, V> | undefined {
    let e: HashMapNode<K, V> | undefined = undefined;

    for (let tableElement of this._table) {
      if (equalsTo(key, tableElement.getKey())) {
        e = tableElement;
        break;
      }
    }
    return e;
  }
}

/**
 * 一个映射条目（键值对）。
 *
 * {@link Map#entrySet}方法返回地图的集合视图，其元素属于此类。
 *
 * @param K 存入地图的键类型
 * @param V 存入地图的值类型
 * @see HashMap
 * @see Entry
 * @version 1.0
 */
export class HashMapNode<K, V> extends Obj implements Entry<K, V> {

  /**
   * 此键值对中键的hashCode，我们依赖此hashCode以判断键是否相同。
   *
   * @private
   * @version 1.0
   */
  private readonly hashcode: number;

  /**
   * 此键值对中的键。
   *
   * @private
   * @version 1.0
   */
  private readonly key: K;

  /**
   * 此键值对中的值。
   *
   * @private
   * @version 1.0
   */
  private value: V;

  /**
   * 构造一个键值对，自动计算其键、值和键的hashcode。
   *
   * @param hashcode 键的hashcode
   * @param key 键
   * @param value 值
   * @protected
   * @version 1.0
   */
  public constructor(hashcode: number, key: K, value: V) {
    super();
    this.hashcode = hashcode;
    this.key = key;
    this.value = value;
  }

  /**
   * 返回与此条目的键。
   *
   * @return {K} 返回与此条目的键
   * @version 1.0
   */
  public getKey(): K {
    return this.key;
  }

  /**
   * 返回此条目的值。
   *
   * 若此条目已经在映射中被删除，则返回undefined。
   *
   * @return {V | undefined} 此条目的值。若此条目已经被删除，则返回undefined
   * @version 1.0
   */
  public getValue(): V {
    return this.value;
  }

  /**
   * 用指定的值替换与此条目对应的值，并返回原本的值。
   *
   * 如果映射已从映射中删除，则此调用将不产生任何作用（因为在原映射中已不再有此映射）。
   *
   * @param value 需要关联的新值
   * @return {V | undefined} 此条目中的旧值。若此条目已经被删除，则返回undefined
   * @version 1.0
   */
  public setValue(value: V): V {
    const oldValue = this.value;
    this.value = value;
    return oldValue;
  }

  public equals(b: Obj): boolean {
    if (b instanceof HashMapNode) {
      return equalsTo(this.hashcode, b.hashcode);
    }
    return false;
  }

  public hashCode(): number {
    return this.hashcode;
  }

  /**
   * 构造一个{@link Entry}。
   *
   * @param k 键
   * @param v 值
   * @return {HashMapNode<K, V>} 映射
   * @version 1.0
   */
  public static of<K, V>(k: K, v: V): HashMapNode<K, V> {
    return new HashMapNode<K, V>(hash(k), k, v);
  }
}
